constrained_type.hpp
namespace type_safe
{
//=== Constrained type ===//
struct assertion_verifier;
class constrain_error;
struct throwing_verifier;
template <typename T, typename Constraint, class Verifier = assertion_verifier>
class constrained_type;
template <typename T, class Constraint, class Verifier>
class constrained_type<T&, Constraint, Verifier>;
template <typename T, class Constraint, class Verifier = assertion_verifier>
using constrained_ref = constrained_type<T&, Constraint, Verifier>;
template <typename T, class Constraint, class Verifier>
class constrained_modifier;
template <typename T, typename Constraint, class Verifier>
constexpr bool operator==(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
template <typename T, typename Constraint, class Verifier>
constexpr bool operator!=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
template <typename T, typename Constraint, class Verifier>
constexpr bool operator<(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
template <typename T, typename Constraint, class Verifier>
constexpr bool operator<=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
template <typename T, typename Constraint, class Verifier>
constexpr bool operator>(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
template <typename T, typename Constraint, class Verifier>
constexpr bool operator>=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
template <class Verifier, typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint, Verifier> constrain(T&& value, Constraint c);
template <typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint> constrain(T&& value, Constraint c);
template <typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint, throwing_verifier> sanitize(T&& value, Constraint c);
template <typename T, typename Constraint, class Verifier, typename Func, typename ... Args>
void with(constrained_type<T, Constraint, Verifier>& value, Func&& f, Args&&... additional_args);
//=== Tagged type ===//
struct null_verifier;
template <typename T, class Constraint>
using tagged_type = constrained_type<T, Constraint, null_verifier>;
template <typename T, class Constraint>
using tagged_ref = constrained_ref<T, Constraint, null_verifier>;
template <typename T, typename Constraint>
constexpr tagged_type<typename std::decay<T>::type, Constraint> tag(T&& value, Constraint c);
namespace constraints
{
struct non_null;
class non_empty;
struct non_default;
struct non_invalid;
struct owner;
}
}
type_safe::assertion_verifier
struct assertion_verifier
{
template <typename Value, typename Predicate>
static constexpr typename std::decay<Value>::type verify(Value&& val, const Predicate& p);
};
A Verifier
for ts::constrained_type that DEBUG_ASSERT
s the constraint.
If TYPE_SAFE_ENABLE_PRECONDITION_CHECKS is true
, it will assert that the value fulfills the predicate and returns it unchanged. If assertions are disabled, it will just return the value unchanged.
type_safe::constrain_error
class constrain_error
: public std::logic_error
{
public:
constrain_error();
};
The exception class thrown by the ts::throwing_verifier.
type_safe::throwing_verifier
struct throwing_verifier
{
template <typename Value, typename Predicate>
static constexpr typename std::decay<Value>::type verify(Value&& val, const Predicate& p);
};
A Verifier
for ts::constrained_type that throws an exception in case of failure.
Unlike ts::assertion_verifier, it will always check the constrain. If it is not fulfilled, it throws an exception of type ts::constrain_error, otherwise return the original value unchanged.
Notes: ts::assertion_verifier is the default, because a constrain violation is a logic error, usually done by a programmer. Use this one only if you want to use ts::constrained_type with unsanitized user input, for example.
type_safe::constrained_type
template <typename T, typename Constraint, class Verifier = assertion_verifier>
class constrained_type
{
public:
using value_type = typename std::remove_cv<T>::type;
using constraint_predicate = Constraint;
constexpr constrained_type(const value_type& value, constraint_predicate predicate = {});
constexpr constrained_type(value_type&& value, constraint_predicate predicate = {}) noexcept('hidden');
constexpr constrained_type(const constrained_type& other);
~constrained_type() noexcept = default;
constexpr constrained_type& operator=(const value_type& other);
constexpr constrained_type& operator=(value_type&& other) noexcept('hidden');
constexpr constrained_type& operator=(const constrained_type& other);
friend constexpr void swap(constrained_type& a, constrained_type& b) noexcept('hidden');
template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
constrained_modifier<T, Constraint, Verifier> modify() noexcept;
constexpr value_type&& release() && noexcept;
constexpr const value_type& operator*() const noexcept;
constexpr const value_type* operator->() const noexcept;
constexpr const value_type& get_value() const noexcept;
constexpr const constraint_predicate& get_constraint() const noexcept;
};
A value of type T
that always fulfills the predicate Constraint
.
The Constraint
is checked by the Verifier
. The Constraint
can also provide a nested template is_valid<T>
to statically check types. Those will be checked regardless of the Verifier
.
If T
is const
, the modify()
function will not be available, you can only modify the type by assigning a completely new value to it.
Requires: T
must not be a reference, Constraint
must be a moveable, non-final class where no operation throws, and Verifier
must provide a static
function [const] T[&] verify(const T&, const Predicate&)
. The return value is stored and it must always fulfill the predicate. It also requires that no const
operation on T
may modify it in a way that the predicate isn't fulfilled anymore. \notes Additional requirements of the Constraint
depend on the Verifier
used. If not stated otherwise, a Verifier
in this library requires that the Constraint
is a Predicate
for T
.
type_safe::constrained_type::constrained_type
(1) constexpr constrained_type(const value_type& value, constraint_predicate predicate = {});
(2) constexpr constrained_type(value_type&& value, constraint_predicate predicate = {}) noexcept('hidden');
Effects: Creates it giving it a valid value
and a predicate
. The value
will be copied(1)/moved(2) and verified.
Throws: Anything thrown by the copy(1)/move(2) constructor of value_type
or the Verifier
if the value
is invalid.
type_safe::constrained_type::constrained_type
constexpr constrained_type(const constrained_type& other);
Effects: Copies the value and predicate of other
.
Throws: Anything thrown by the copy constructor of value_type
.
Requires: Constraint
must be copyable.
type_safe::constrained_type::~constrained_type
~constrained_type() noexcept = default;
Effects: Destroys the value.
type_safe::constrained_type::operator=
constexpr constrained_type& operator=(const value_type& other);
Effects: Same as assigning constrained_type(other, get_constraint()).release()
to the stored value. It will invoke copy(1)/move(2) constructor followed by move assignment operator. \throws Anything thrown by the copy(1)/move(2) constructor or move assignment operator of value_type
, or the Verifier
if the value
is invalid. If the value
is invalid, nothing will be changed. \requires Constraint
must be copyable. \group assign_value
type_safe::constrained_type::operator=
(1) constexpr constrained_type& operator=(value_type&& other) noexcept('hidden');
type_safe::constrained_type::operator=
constexpr constrained_type& operator=(const constrained_type& other);
Effects: Copies the value and predicate from other
.
Throws: Anything thrown by the copy assignment operator of value_type
.
Requires: Constraint
must be copyable.
type_safe::swap
friend constexpr void swap(constrained_type& a, constrained_type& b) noexcept('hidden');
Effects: Swaps the value and predicate of a a
and b
.
Throws: Anything thrown by the swap function of value_type
.
Requires: Constraint
must be swappable.
type_safe::constrained_type::modify
template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
constrained_modifier<T, Constraint, Verifier> modify() noexcept;
Returns: A proxy object to provide verified write-access to the stored value.
Notes: This function does not participate in overload resolution if T
is const
.
type_safe::constrained_type::release
constexpr value_type&& release() && noexcept;
Effects: Moves the stored value out of the constrained_type
, it will not be checked further.
Returns: An rvalue reference to the stored value.
Notes: After this function is called, the object must not be used anymore except as target for assignment or in the destructor.
type_safe::constrained_type::operator*
constexpr const value_type& operator*() const noexcept;
Dereference operator.
Returns: A const
reference to the stored value.
type_safe::constrained_type::operator->
constexpr const value_type* operator->() const noexcept;
Member access operator.
Returns: A const
pointer to the stored value.
type_safe::constrained_type::get_value
constexpr const value_type& get_value() const noexcept;
Returns: A const
reference to the stored value.
type_safe::constrained_type::get_constraint
constexpr const constraint_predicate& get_constraint() const noexcept;
Returns: The predicate that determines validity.
type_safe::constrained_type<T&, Constraint, Verifier>
template <typename T, class Constraint, class Verifier>
class constrained_type<T&, Constraint, Verifier>
{
public:
using value_type = T;
using constraint_predicate = Constraint;
constexpr constrained_type(T& value, constraint_predicate predicate = {});
template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
constrained_modifier<T&, Constraint, Verifier> modify() noexcept;
constexpr const value_type& operator*() const noexcept;
constexpr const value_type* operator->() const noexcept;
constexpr const value_type& get_value() const noexcept;
constexpr const constraint_predicate& get_constraint() const noexcept;
};
Specialization of ts::constrained_type for references.
It models a reference to a value that always fulfills the given constraint. The value must not be changed by other means, it is thus perfect for function parameters.
type_safe::constrained_type<T&, Constraint, Verifier>::constrained_type
constexpr constrained_type(T& value, constraint_predicate predicate = {});
Effects: Binds the reference to the given object.
type_safe::constrained_type<T&, Constraint, Verifier>::modify
template <typename Dummy = T, typename = typename std::enable_if<!std::is_const<Dummy>::value>::type>
constrained_modifier<T&, Constraint, Verifier> modify() noexcept;
Returns: A proxy object to provide verified write-access to the referred value.
Notes: This function does not participate in overload resolution if T
is const
.
type_safe::constrained_type<T&, Constraint, Verifier>::operator*
constexpr const value_type& operator*() const noexcept;
Dereference operator.
Returns: A const
reference to the referred value.
type_safe::constrained_type<T&, Constraint, Verifier>::operator->
constexpr const value_type* operator->() const noexcept;
Member access operator.
Returns: A const
pointer to the referred value.
type_safe::constrained_type<T&, Constraint, Verifier>::get_value
constexpr const value_type& get_value() const noexcept;
Returns: A const
reference to the referred value.
type_safe::constrained_type<T&, Constraint, Verifier>::get_constraint
constexpr const constraint_predicate& get_constraint() const noexcept;
Returns: The predicate that determines validity.
type_safe::constrained_ref
template <typename T, class Constraint, class Verifier = assertion_verifier>
using constrained_ref = constrained_type<T&, Constraint, Verifier>;
Alias for ts::constrained_type<T&>.
type_safe::constrained_modifier
template <typename T, class Constraint, class Verifier>
class constrained_modifier
{
public:
using value_type = typename constrained_type<T, Constraint, Verifier>::value_type;
constrained_modifier(constrained_modifier&& other) noexcept;
~constrained_modifier() noexcept(false);
constrained_modifier& operator=(constrained_modifier&& other) noexcept;
value_type& operator*() noexcept;
value_type* operator->() noexcept;
value_type& get() noexcept;
};
A proxy class to provide write access to the stored value of a ts::constrained_type.
The destructor will verify the value again.
type_safe::constrained_modifier::constrained_modifier
constrained_modifier(constrained_modifier&& other) noexcept;
Effects: Move constructs it. other
will not verify any value afterwards.
type_safe::constrained_modifier::~constrained_modifier
~constrained_modifier() noexcept(false);
Effects: Verifies the value, if there is any.
type_safe::constrained_modifier::operator=
constrained_modifier& operator=(constrained_modifier&& other) noexcept;
Effects: Move assigns it. other
will not verify any value afterwards.
type_safe::constrained_modifier::operator*
value_type& operator*() noexcept;
Dereference operator.
Returns: A reference to the stored value.
Requires: It must not be in the moved-from state.
type_safe::constrained_modifier::operator->
value_type* operator->() noexcept;
Member access operator.
Returns: A pointer to the stored value.
Requires: It must not be in the moved-from state.
type_safe::constrained_modifier::get
value_type& get() noexcept;
Returns: A reference to the stored value.
Requires: It must not be in the moved-from state.
(1) template <typename T, typename Constraint, class Verifier>
constexpr bool operator==(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
(2) template <typename T, typename Constraint, class Verifier>
constexpr bool operator!=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
(3) template <typename T, typename Constraint, class Verifier>
constexpr bool operator<(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
(4) template <typename T, typename Constraint, class Verifier>
constexpr bool operator<=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
(5) template <typename T, typename Constraint, class Verifier>
constexpr bool operator>(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
(6) template <typename T, typename Constraint, class Verifier>
constexpr bool operator>=(const constrained_type<T, Constraint, Verifier>& lhs, const constrained_type<T, Constraint, Verifier>& rhs) noexcept('hidden');
Compares a ts::constrained_type.
Returns: The result of the comparison of the underlying value.
Notes: The comparison operators do not participate in overload resolution, unless the stored type provides them as well.
type_safe::constrain
template <class Verifier, typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint, Verifier> constrain(T&& value, Constraint c);
Creates a ts::constrained_type.
Returns: A ts::constrained_type with the given value
, Constraint
and Verifier
.
type_safe::constrain
template <typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint> constrain(T&& value, Constraint c);
Creates a ts::constrained_type with the default verifier, ts::assertion_verifier.
Returns: A ts::constrained_type with the given value
and Constraint
.
Requires: As it uses a DEBUG_ASSERT
to check constrain, the value must be valid.
type_safe::sanitize
template <typename T, typename Constraint>
constexpr constrained_type<typename std::decay<T>::type, Constraint, throwing_verifier> sanitize(T&& value, Constraint c);
Creates a ts::constrained_type using the ts::throwing_verifier.
Returns: A ts::constrained_type with the given value
and Constraint
.
Throws: A ts::constrain_error if the value
isn't valid, or anything else thrown by the constructor.
Notes: This is meant for sanitizing user input, using a recoverable error handling strategy.
type_safe::with
template <typename T, typename Constraint, class Verifier, typename Func, typename ... Args>
void with(constrained_type<T, Constraint, Verifier>& value, Func&& f, Args&&... additional_args);
With operation for ts::constrained_type.
Effects: Calls f
with a non-const
reference to the stored value of the ts::constrained_type. It checks that f
does not change the validity of the object. \notes The same behavior can be accomplished by using the modify()
member function.
type_safe::null_verifier
struct null_verifier
{
template <typename Value, typename Predicate>
static constexpr Value&& verify(Value&& v, const Predicate&);
};
A Verifier
for ts::constrained_type that doesn't check the constraint.
It will simply return the value unchanged, without any checks.
Notes: It does not impose any additional requirements on the Predicate
.
type_safe::tagged_type
template <typename T, class Constraint>
using tagged_type = constrained_type<T, Constraint, null_verifier>;
An alias for ts::constrained_type that never checks the constraint.
It is useful for creating tagged types: The Constraint
- which does not need to be a predicate anymore - is a "tag" to differentiate a type in different states. For example, you could have a "sanitized" value and a "non-sanitized" value that have different types, so you cannot accidentally mix them. \notes It is only intended if the Constraint
cannot be formalized easily and/or is expensive. Otherwise ts::constrained_type is recommended as it does additional runtime checks in debug mode.
type_safe::tagged_ref
template <typename T, class Constraint>
using tagged_ref = constrained_ref<T, Constraint, null_verifier>;
An alias for ts::tagged_type with reference.
type_safe::tag
template <typename T, typename Constraint>
constexpr tagged_type<typename std::decay<T>::type, Constraint> tag(T&& value, Constraint c);
Creates a new ts::tagged_type.
Returns: A ts::tagged_type with the given value
and Constraint
.
type_safe::constraints::non_null
struct non_null
{
template <typename T>
struct is_valid
: std::true_type
{
};
template <typename T>
constexpr bool operator()(const T& ptr) const noexcept;
};
A Constraint
for the ts::constrained_type.
A value of a pointer type is valid if it is not equal to nullptr
. This is borrowed from GSL's non_null.
type_safe::constraints::non_empty
class non_empty
{
public:
template <typename T>
constexpr bool operator()(const T& t) const;
};
A Constraint
for the ts::constrained_type.
A value of a container type is valid if it is not empty. Empty-ness is determined with either a member or non-member function.
type_safe::constraints::non_default
struct non_default
{
template <typename T>
constexpr bool operator()(const T& t) const noexcept('hidden');
};
A Constraint
for the ts::constrained_type.
A value is valid if it not equal to the default constructed value.
type_safe::constraints::non_invalid
struct non_invalid
{
template <typename T>
constexpr bool operator()(const T& t) const noexcept('hidden');
};
A Constraint
for the ts::constrained_type.
A value of a pointer-like type is valid if the expression !value
is false
.
type_safe::constraints::owner
struct owner
{
};
A Constraint
for the ts::tagged_type.
It marks an owning pointer. It is borrowed from GSL's non_null.
Notes: This is not actually a predicate.